﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Text;
using Templates.SharpParts;
using Templates.SharpParts.Shells;

namespace Templates
{
	[DataContract]
	[KnownType(typeof(UsingNamespaceShell))]
	[KnownType(typeof(PropertyShell))]
	[KnownType(typeof(ClassShell))]
	[KnownType(typeof(TempateCollectionShell))]
	public abstract class T4TemplateBase 
	{
		protected T4TemplateBase()
		{
			ToStringHelper = new ToStringInstanceHelper();
			GenerationEnvironment = new StringBuilder();
			_namespaces = new HashSet<string>();
			BlockIds = new List<Guid>();

			ShellNamespaces = new List<string>();
		}

		private Guid _id;

		[DataMember()]
		public Guid Id
		{
			get
			{
				if (_id == Guid.Empty) _id = Guid.NewGuid();

				return _id;
			}
			set { _id = value; }
		}

		public List<Guid> BlockIds { get; private set; }

		private int _space = 0;

		[DataMember]
		public T4TemplateBase Child { get; set; }

		private readonly HashSet<string> _namespaces;
		public ReadOnlyCollection<string> Namespaces { get { return _namespaces.OrderBy(x => x).ToList().AsReadOnly(); } }

		protected StringBuilder GenerationEnvironment { get; set; }
		protected string ChildString { get; set; }


		public IEnumerable<string> AdditionalNamespaces { set { value.ToList().ForEach(AddNamespace); } }
		public string AdditionalNamespace { set { AddNamespace(value); } }

		private void AddNamespace(string ns)
		{
			if (!ShellNamespaces.Any(x => x == ns))
				ShellNamespaces.Add(ns);
			if (!string.IsNullOrWhiteSpace(ns) && !_namespaces.Contains(ns))
				_namespaces.Add(ns);
		}

		[DataMember] public List<string> ShellNamespaces { get; set; }

		protected void CopyNamespace(T4TemplateBase template)
		{
			AdditionalNamespaces = template._namespaces;
		}

		protected void AddBlockId(Guid id)
		{
			if (!BlockIds.Any(x => x == id))
				BlockIds.Add(id);
		}

		public virtual void BeforeToString(){}
		public override string ToString()
		{
			AdditionalNamespaces = ShellNamespaces;
			AddBlockId(Id);

			if (Child != null)
			{
				Child.BlockIds = BlockIds;
				Child._space = _space + 1;
				ChildString = Child.ToString();


				AdditionalNamespaces = Child._namespaces;
			}

			BeforeToString();

			GenerationEnvironment.Clear();
			string transformText = TransformText();

			return Indent(transformText);
		}

		private string Indent(string str)
		{
			string indent = string.Empty;
			for (int i = 0; i < _space; i++)
				indent += "\t";

			return (indent + str).Replace("\r\n", "\r\n" + indent);
		}

		public abstract string TransformText();

		protected void Write(string textToAppend)
		{
			if (GenerationEnvironment == null)
				GenerationEnvironment = new StringBuilder();
			if (string.IsNullOrEmpty(textToAppend))
				return;

			GenerationEnvironment.Append(textToAppend);
		}

		protected void WriteLine(string textToAppend)
		{
			if (string.IsNullOrEmpty(textToAppend))
				return;

			Write(textToAppend);
			GenerationEnvironment.AppendLine();
		}

		#region ToString Helpers

		public ToStringInstanceHelper ToStringHelper { get; private set; }

		/// <summary>
		/// Utility class to produce culture-oriented representation of an object as a string.
		/// </summary>
		public class ToStringInstanceHelper
		{
			private IFormatProvider _formatProviderField = CultureInfo.InvariantCulture;
			/// <summary>
			/// Gets or sets format provider to be used by ToStringWithCulture method.
			/// </summary>
			public IFormatProvider FormatProvider
			{
				get
				{
					return this._formatProviderField;
				}
				set
				{
					if ((value != null))
					{
						this._formatProviderField = value;
					}
				}
			}
			/// <summary>
			/// This is called from the compile/run appdomain to convert objects within an expression block to a string
			/// </summary>
			public string ToStringWithCulture(object objectToConvert, IFormatProvider formatProviderField = null)
			{
				if ((objectToConvert == null))
					return string.Empty;

				if (formatProviderField == null)
					formatProviderField = CultureInfo.InvariantCulture;

				Type t = objectToConvert.GetType();

				MethodInfo toStringMethod = (from type in t.Assembly.GetTypes()
				                             where type.IsSealed && !type.IsGenericType && !type.IsNested
				                             from m in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
				                             where m.Name == "ToString" && m.IsDefined(typeof (ExtensionAttribute), false) && m.IsDefined(typeof (OverrideAttribute), false)
				                             where m.GetParameters()[0].ParameterType == t
				                             select m).FirstOrDefault();

				if (toStringMethod != null)
					return ((string)(toStringMethod.Invoke(objectToConvert, new[] { objectToConvert })));

				toStringMethod = t.GetMethod("ToString", new[] { typeof(IFormatProvider) });
				if ((toStringMethod == null))
					return objectToConvert.ToString();

				return ((string)(toStringMethod.Invoke(objectToConvert, new object[] { formatProviderField })));
			}
		}

		#endregion


		public override bool Equals(object other)
		{
			if (other == null)
				return false;
			return GetHashCode() == other.GetHashCode();
		}

		public override int GetHashCode()
		{
			throw new NotImplementedException();
		}

		public virtual T4TemplateBase GetBlockById(Guid blockId)
		{
			if (this.Id == blockId)
				return this;

			if (Child == null)
				return null;

			return Child.GetBlockById(blockId);
		}

	}

	internal static class HashSetExtention
	{
		internal static void AddRange<T>(this HashSet<T> hashSet, IEnumerable<T> collection)
		{
			var enumerator = collection.GetEnumerator();
			while (enumerator.MoveNext())
			{
				hashSet.Add(enumerator.Current);
			}
		}
	}
}